package model.ray;
 
import java.util.ArrayList; 

import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import model.AABBConstants;

public class Ray implements AABBConstants{
	
	public Point3d origin;
	public Vector3d direction;

	private Clasification rayClass;
	private ArrayList<Double> slopes = new ArrayList<Double>();
	private ArrayList<Double> precomputation= new ArrayList<Double>();
	private Double impactDist;
	
	public Ray(Point3d origin,Vector3d direction){
		this.origin = new Point3d(origin);
		this.direction = direction;
		this.impactDist = Double.MAX_VALUE;
		
		if (this.direction != null) {
			precompute();
		}
	}
	
	public Vector3d getIntersectionPoint(){
		Vector3d p = new Vector3d();
		
		if( impactDist == Double.MAX_VALUE )
			return p;
		
		Vector3d aux = new Vector3d(direction);
	    aux.scale(impactDist);
	    aux.add(origin);
	    p.set(aux.x,aux.y,aux.z);
		
	    setSigns();

		return  p;
	}
	
	public enum Signs {
		MMM, MMP, MPM, MPP, PMM, PMP, PPM, PPP, NONE
	};
	public Signs signs;
	void setSigns() {
		if (direction == null)
			signs = Signs.NONE;
		else if (direction.x < 0 && direction.y < 0 && direction.z < 0)
			signs = Signs.MMM;
		else if (direction.x < 0 && direction.y < 0 && direction.z >= 0)
			signs = Signs.MMP;
		else if (direction.x < 0 && direction.y >= 0 && direction.z < 0)
			signs = Signs.MPM;
		else if (direction.x < 0 && direction.y >= 0 && direction.z >= 0)
			signs = Signs.MPP;
		else if (direction.x >= 0 && direction.y < 0 && direction.z < 0)
			signs = Signs.PMM;
		else if (direction.x >= 0 && direction.y < 0 && direction.z >= 0)
			signs = Signs.PMP;
		else if (direction.x >= 0 && direction.y >= 0 && direction.z < 0)
			signs = Signs.PPM;
		else if (direction.x >= 0 && direction.y >= 0 && direction.z >= 0)
			signs = Signs.PPP;
	}
	private void precompute() {
		
		this.slopes.add(0,0.0);
		this.slopes.add(Slope.IJ.ordinal(), this.direction.x/this.direction.y);
		this.slopes.add(Slope.JI.ordinal(), this.direction.y/this.direction.x);
		this.slopes.add(Slope.JK.ordinal(), this.direction.y/this.direction.z);
		this.slopes.add(Slope.KJ.ordinal(), this.direction.z/this.direction.y);
		this.slopes.add(Slope.IK.ordinal(), this.direction.x/this.direction.z);
		this.slopes.add(Slope.KI.ordinal(), this.direction.z/this.direction.x);
		
		this.precomputation.add(0,0.0);
		this.precomputation.add(Faces.XY.ordinal(), origin.y - this.slopes.get(Faces.XY.ordinal())*origin.x);
		this.precomputation.add(Faces.YX.ordinal(), origin.x - this.slopes.get(Faces.YX.ordinal())*origin.y);
		this.precomputation.add(Faces.ZY.ordinal(), origin.y - this.slopes.get(Faces.ZY.ordinal())*origin.z);
		this.precomputation.add(Faces.YZ.ordinal(), origin.z - this.slopes.get(Faces.YZ.ordinal())*origin.y);
		this.precomputation.add(Faces.XZ.ordinal(), origin.z - this.slopes.get(Faces.XZ.ordinal())*origin.x);
		this.precomputation.add(Faces.ZX.ordinal(), origin.x - this.slopes.get(Faces.ZX.ordinal())*origin.z);
		
		this.rayClass = determineClass();

	}
	
	public Clasification getClasification(){
		return this.rayClass;
	}
	
	public Double getSlope(Slope slope){
		return this.slopes.get(slope.ordinal());
	}
	
	public Double getPrecomputation(Faces face){
		return this.precomputation.get(face.ordinal());
	}

	public void setOrigin(Point3d origin) {
		this.origin = new Point3d(origin);
		
		if (this.direction != null) {
			precompute();
		}
	}
	
	public Point3d getOrigin() {
		return new Point3d(origin);
	}
	
	public void setDirection(Vector3d direction) 
	{
		this.direction = new Vector3d(direction);

		if( slopes.size() > 0 ){
			this.slopes.set(Slope.IJ.ordinal(), this.direction.x/this.direction.y);
			this.slopes.set(Slope.JI.ordinal(), this.direction.y/this.direction.x);
			this.slopes.set(Slope.JK.ordinal(), this.direction.y/this.direction.z);
			this.slopes.set(Slope.KJ.ordinal(), this.direction.z/this.direction.y);
			this.slopes.set(Slope.IK.ordinal(), this.direction.x/this.direction.z);
			this.slopes.set(Slope.KI.ordinal(), this.direction.z/this.direction.x);
			
			this.precomputation.set(0,0.0);
			this.precomputation.set(Faces.XY.ordinal(), origin.y - this.slopes.get(Faces.XY.ordinal())*origin.x);
			this.precomputation.set(Faces.YX.ordinal(), origin.x - this.slopes.get(Faces.YX.ordinal())*origin.y);
			this.precomputation.set(Faces.ZY.ordinal(), origin.y - this.slopes.get(Faces.ZY.ordinal())*origin.z);
			this.precomputation.set(Faces.YZ.ordinal(), origin.z - this.slopes.get(Faces.YZ.ordinal())*origin.y);
			this.precomputation.set(Faces.XZ.ordinal(), origin.z - this.slopes.get(Faces.XZ.ordinal())*origin.x);
			this.precomputation.set(Faces.ZX.ordinal(), origin.x - this.slopes.get(Faces.ZX.ordinal())*origin.z);
			
			this.rayClass = determineClass();
		} else
			precompute();

	}
	
	public Vector3d getDirection() {
		return new Vector3d(direction);
	}
	
	public Point3d getPoint(double t){
		double x, y, z;
		x = this.origin.x + this.getDirection().x * t;
		y = this.origin.y + this.getDirection().y * t;
		z = this.origin.z + this.getDirection().z * t;
		
		return new Point3d(x,y,z);
	}
	
	public double getDistance(Point3d p) {
		
		if (p == null) {
			throw new IllegalArgumentException("null point");
		}
		
		return this.origin.distanceSquared(p);
//		Vector3d vec = new Vector3d();
//		vec.sub(this.origin, p);
//		return vec.length();
	}
	
	@Override
	public String toString(){
		return "Origen: (" + this.origin.x + ", "  + this.origin.y + ", " + this.origin.z + ") " +
			   "Direccion: (" + this.direction.x + ", "  + this.direction.y + ", " + this.direction.z + ") ";
	}
	
	private Clasification determineClass() {
		
		if( this.direction.x <0 ){
			if (this.direction.y < 0 ){
				if( this.direction.z <0 )
					return Clasification.MMM;
				else if(this.direction.z >0)
					return Clasification.MMP;
				else
					return Clasification.MMO;
			}
			else { // y >= 0
				if( this.direction.z < 0){
					if(this.direction.y==0)
						return Clasification.MOM;
					return Clasification.MPM;
				} else { //z >=0
					if(this.direction.y==0 && this.direction.z==0)
						return Clasification.MOO;
					else if( this.direction.z==0 )
						return Clasification.MPO;
					else if ( this.direction.y==0)
						return Clasification.MOP;
					else
						return Clasification.MPP;
				}
			}
		} else { // x>=0
			if(this.direction.y<0){
				if( this.direction.z <0 ){
					if( this.direction.x==0)
						return Clasification.OMM;
					return Clasification.PMM; 
				} else { //z >= 0
					if( this.direction.x==0 && this.direction.z==0)
						return Clasification.OMO;
					else if ( this.direction.z==0)
						return Clasification.PMO;
					else if (this.direction.x==0)
						return Clasification.OMP;
					else
						return Clasification.PMP;
				}
			} else { // y>=0
				if(this.direction.z<0){
					if(this.direction.x==0 && this.direction.y==0)
						return Clasification.OOM;
					else if(this.direction.x==0)
						return Clasification.OPM;
					else if(this.direction.y==0)
						return Clasification.POM;
					else
						return Clasification.PPM;
				} else {// z >=0
					if( this.direction.x==0){
						if( this.direction.y==0)
							return Clasification.OOP;
						else if( this.direction.z==0 )
							return Clasification.OPO;
						else
							return Clasification.OPP;
					} else {
						if( this.direction.y==0 && this.direction.z==0)
							return Clasification.POO;
						else if(this.direction.y==0)
							return Clasification.POP;
						else if(this.direction.z==0)
							return Clasification.PPO;
						else
							return Clasification.PPP;
					}
				}
				
			}
		}
	}

	public Double getImpactDist() {
		return impactDist;
	}
	

}
